import base64
import json
import re
import time
import uuid
from datetime import datetime
from datetime import timedelta
from typing import Tuple
from urllib.parse import urlparse
import cv2
import numpy as np
import requests

import os, io, tempfile
from PIL import Image, ImageOps, ImageCms, UnidentifiedImageError

# ✅ 尝试注册 pillow-heif（如果存在）
try:
    from pillow_heif import register_heif_opener
    register_heif_opener()
    HAS_PILLOW_HEIF = True
except ImportError:
    HAS_PILLOW_HEIF = False

# ✅ fallback: pyheif
try:
    import pyheif
    from pyheif.error import HeifError
    HAS_PYHEIF = True
except ImportError:
    HAS_PYHEIF = False


def translate_text(prompt, target_language="English"):
    import requests

    url = "http://174.142.205.24:5000/translate"
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer en2GFaFzpYioEv0VZVGpdj2uZ"
    }
    data = {
        "prompt": prompt,
        "target_language": target_language
    }

    try:
        response = requests.post(url, headers=headers, json=data)
        response.raise_for_status()
        result = response.json()
        return result  # 返回包含 translated_prompt 的字典
    except Exception as e:
        print(f"翻译失败: {e}")
        return prompt  # 翻译失败时返回原始文本


# 将图像转换为JPG格式
def convert_to_jpg(input_image_path, out_image_path):
    """
    根据图像文件头将图像转换为JPG格式

    :param input_image_path: 图像的字节数据
    :param out_image_path: 转换后JPG图像的保存路径
    """
    try:
        # 使用Pillow库打开图像
        image = Image.open(input_image_path)
        # 将图像转换为RGB模式
        image = image.convert('RGB')
        # 保存为JPG格式的图像文件
        image.save(out_image_path, 'JPEG')
        print(f"图像已成功转换为JPG格式，保存路径为：{out_image_path}")
        return True
        print(f"图像已成功转换为JPG格式，保存路径为：{out_image_path}")
    except Exception as e:
        print(f"图像转换过程中出现错误：{e}")
        return False


# 模型允许的图片类型白名单,
def get_image_types_whitelist():
    file_type = {"PNG", "JPEG"}
    return file_type


def get_image_type_by_header(file_path):
    file_headers = {
        b'\x89PNG\r\n\x1a\n': 'PNG',
        b'\xff\xd8\xff\xe0': 'JPEG',  # 基本的JPEG SOI标记
        b'\xff\xd8\xff\xdb': 'JFIF',  # 基本的JPEG SOI标记
        b'RIFF>\x94\x00\x00': 'WebP',
        b'\x00\x00\x00\x1cftyp': 'HEIF',
        b'BM\xbeK\x14\x00\x00\x00': 'BMP',
        # 补充其他图片类型
        b'GIF87a': 'GIF',
        b'GIF89a': 'GIF',
        b'\x00\x00\x00\x18ftypavif': 'AVIF',
        b'\x49\x49\x2A\x00': 'TIFF',
        b'\x4D\x4D\x00\x2A': 'TIFF',
        b'\x46\x4C\x49\x46': 'FLI/FLC',  # FLI/FLC是动画光栅图像格式
        b'\x52\x49\x46\x46': 'WebP',  # WebP文件头
        b'\x42\x4C\x45\x4E\x44\x45\x58': 'BLENDER',  # Blender图像格式
        b'\x01\xDA\x01\x01': 'TARGA',  # TARGA文件头
        b'\x00\x01\x00\x00\x00': 'PCX',  # PCX文件头
        b'\x0A': 'SGI',  # SGI文件头
        b'\x76\x69\x64\x73\x2E\x35\x2E\x31\x0A': 'VIDS',  # VIDS文件头
        b'\x49\x49\x2C\x00': 'TIFF',  # TIFF文件头（大端序）
        b'\xff\x51': 'ICO',  # ICO文件头
        b'\x8B\x4C\x69\x73\x74': 'LZW',  # LZW压缩的图像文件头
    }
    with open(file_path, 'rb') as f:
        file_header = f.read(12)
        print(f"文件类型：{file_header}")
    for header, file_type in file_headers.items():
        if file_header.startswith(header):
            return file_type
    return "unknown"


# 用于检查图像类型是否在白名单中
def check_image_whitelist(file_path):
    whitelisted_types = get_image_types_whitelist()
    file_type = get_image_type_by_header(file_path)
    if file_type in whitelisted_types:
        print(f"文件类型：{file_type}")
        return True, file_type
    return False, file_type


def compress_image(output_path, input_path, quality=80):
    """
    Compress an image file using the optimal library based on its format.

    :param input_path: Path to the input image file.
    :param output_path: Path to the output compressed image file.
    :param quality: Quality of the output image (1-100 for JPEG, 0-9 for PNG). Default is 90.
    """
    with Image.open(input_path) as img:
        max_img_size = max(img.size)
        if max_img_size >= 900:
            quality = quality
        else:
            quality = 100
        if img is not None:
            # Ensure quality is within the correct range for PNG
            quality = max(0, min(quality, 100))
            img.save(output_path, quality=quality)
        else:
            print("Failed to read the image.")


def watermarking_topLeft(FreePic_path, watermark_path):
    with Image.open(FreePic_path) as img:
        width, height = img.size
        with Image.open(watermark_path) as logo:
            max_img_size = max(logo.size)
            if max_img_size >= 900:
                quality = 80
            else:
                quality = 100
            logoWidth, logoHeight = logo.size
            if width > height:
                logo = logo.resize((int(width / 16 * 7), int(logoHeight / (logoWidth / width * 16 / 7))))
                layer = Image.new('RGBA', img.size, (0, 0, 0, 0))
                layer.paste(logo, (int(width / 64), int(width / 64)))
            else:
                # 有可能溢出
                if width > int(height / 16 * 7):
                    logo = logo.resize((int(height / 16 * 7), int(logoHeight / (logoWidth / height * 16 / 7))))
                else:
                    logo = logo.resize((int(width - 30), int(logoHeight / (logoWidth / (width - 30)))))

                layer = Image.new('RGBA', img.size, (0, 0, 0, 0))
                layer.paste(logo, (int(height / 64), int(height / 64)))

        out = Image.composite(layer, img, layer)
        out.save(FreePic_path, quality=quality)


def resize_image_to_base64(input_path, scale_factor=2, resample=Image.LANCZOS):
    """
    调整图片尺寸并返回Base64编码数据，同时限制尺寸范围

    参数:
        input_path: 输入图片路径
        scale_factor: 缩放倍数(如2.0表示放大2倍)
        resample: 重采样方法(默认为Image.LANCZOS，高质量)

    返回:
        (成功状态, 图片Base64编码/错误信息)
    """
    try:

        with Image.open(input_path) as img:
            width, height = img.size
            img_format = img.format or 'JPEG'
            if scale_factor == 1 or scale_factor == 2:
                with open(input_path, 'rb') as f:
                    base64_data = base64.b64encode(f.read()).decode('utf-8')
                    return True, base64_data
            # 计算原始缩放尺寸（兼容原逻辑：scale_factor=2时不缩放）
            scale = scale_factor / 2
            raw_width = int(width * scale)
            raw_height = int(height * scale)

            # 应用尺寸限制（保持宽高比）
            new_width, new_height = apply_size_limits(raw_width, raw_height)

            # 调整尺寸
            resized_img = img.resize((new_width, new_height), resample=resample)

            # 转换为Base64
            buffer = io.BytesIO()
            resized_img.save(buffer, format=img_format)
            img_bytes = buffer.getvalue()
            base64_data = base64.b64encode(img_bytes).decode('utf-8')

            print(f"原始尺寸: {width}×{height}, 调整后尺寸: {new_width}×{new_height}")
            return True, base64_data

    except Exception as e:
        print(f"图片调整失败: {e}")
        return False, str(e)


def compress_and_resize_image(input_path, output_path=None, target_size=3.5, quality=80, max_size=1920):
    output_path = output_path or input_path

    try:
        file_size = os.path.getsize(input_path) / (1024 * 1024)
        if file_size <= target_size:
            print(f"图片大小 {file_size:.2f}MB 已小于目标大小 {target_size}MB，跳过处理")
            return True, input_path

        with Image.open(input_path) as img:
            width, height = img.size
            if max(width, height) > max_size:
                ratio = max_size / max(width, height)
                new_size = (int(width * ratio), int(height * ratio))
                img = img.resize(new_size, Image.LANCZOS)

            original_ext = os.path.splitext(input_path)[1].lower()
            save_kwargs = {}

            if original_ext == '.png':
                # 转换为 RGB 模式并保存为 JPEG 格式
                img = img.convert('RGB')
                save_kwargs = {
                    'format': 'JPEG',
                    'quality': quality,
                    'optimize': True
                }
            elif original_ext in ('.jpg', '.jpeg'):
                save_kwargs = {
                    'format': 'JPEG',
                    'quality': quality,
                    'optimize': True
                }
            else:
                raise ValueError(f"不支持的图片格式：{original_ext}")

            if original_ext in ('.jpg', '.jpeg'):
                current_quality = quality
                while True:
                    img.save(output_path, **save_kwargs)
                    size_mb = os.path.getsize(output_path) / (1024 * 1024)
                    if size_mb <= target_size or current_quality < 10:
                        break
                    current_quality -= 5
                    save_kwargs['quality'] = current_quality
            else:
                img.save(output_path, **save_kwargs)

            print(f"压缩完成并覆盖原文件：{input_path}, 新大小：{os.path.getsize(output_path) / 1024 / 1024:.2f}MB")
            return True, output_path

    except Exception as e:
        print(f"压缩失败：{e}")
        return False, e


# 紧密相连平铺
def watermarking_JoinTiled(input_image_path, watermark_path):
    with Image.open(input_image_path) as img:
        width, height = img.size

        logo = Image.open(watermark_path)
        logo_width, logo_height = logo.size
        max_img_size = max(img.size)
        if max_img_size >= 900:
            quality = 80
        else:
            quality = 100
        if logo_width > width:
            max_size = min(img.size)
            ratio = max_size / logo_width  # 计算缩放比例
            logo = logo.resize((int(logo_width * ratio), int(logo_height * ratio)))
            logo_width = int(logo_width * ratio)
            logo_height = int(logo_height * ratio)
            step_size = int(width / max_size)
        elif logo_width > height:
            max_size = min(img.size)
            ratio = max_size / logo_height  # 计算缩放比例
            logo = logo.resize((int(logo_width * ratio), int(logo_height * ratio)))
            logo_width = int(logo_width * ratio)
            logo_height = int(logo_height * ratio)
            step_size = int(width / max_size)
        else:
            step_size = int(width / logo.width)

        x = 0
        y = 0
        layer = Image.new('RGBA', img.size, (0, 0, 0, 0))
        for _ in range(max_img_size // step_size):  # 根据步长循环遍历图片高度
            # 检查水印是否会超出图片边界
            layer.paste(logo, (x, y))
            x += int(logo_width)
            if x >= width:
                x = 0
                y += logo_height
                # 更新水印位置，并添加水印

        if 'A' not in img.mode:
            out = Image.composite(layer, img, layer)
        else:
            out = Image.alpha_composite(img, layer)
        out.save(input_image_path, quality=quality)
        return input_image_path


# 递归删除
def delFiles(path):
    for filepath, dir_names, filenames in os.walk(path):
        for filename in filenames:
            full_path = os.path.join(filepath, filename)
            try:
                if os.path.isfile(full_path):  # Ensure it's a file before deleting
                    os.remove(full_path)
            except Exception as e:
                print(f"Error deleting file {full_path}: {e}")
        for dir_name in dir_names:
            delFiles(os.path.join(filepath, dir_name))


# 计算时间差
def time_difference():
    # 发送HTTP请求获取上海的时间信息
    url = "https://geoss.mobietool.com/loc/v1/"
    response = requests.get(url)
    data = response.json()  # 将返回的JSON数据解析为Python字典
    # 提取UNIX时间戳
    utc_datetime = data['utc_datetime']
    utc_datetime = utc_datetime[:utc_datetime.rfind('.')]
    # 将时间字符串转换为 datetime 对象
    utc_datetime = int(datetime.strptime(utc_datetime, "%Y-%m-%dT%H:%M:%S").timestamp())
    # 获取本地时间
    local_datetime = int(datetime.utcnow().timestamp())
    # 计算时间差
    difference = local_datetime - utc_datetime
    return difference


# 获取时间差，减 n 秒
def getTimes(n):
    # 将时间戳转换为datetime对象
    dt = datetime.utcnow()
    # 将时间戳加上2秒
    dt -= timedelta(seconds=n)
    # 将datetime对象转换回时间戳
    microsecond_timestamp = int(dt.timestamp() * 1000)
    # 将结果存储在`new_microsecond_timestamp`变量中
    return microsecond_timestamp


# 图片色彩处理,透明通道合并，
def process_image(input_image_path, origin_image_path, image_mode, outfile):
    with Image.open(input_image_path) as input_image:
        width, height = input_image.size
        with Image.open(origin_image_path) as img:
            img = img.convert('RGBA')
            img.save(origin_image_path)
            img1 = cv2.imread(origin_image_path, cv2.IMREAD_UNCHANGED)
            resized_image = cv2.resize(img1, (width, height))
            b, g, r, a = cv2.split(resized_image)
        if image_mode == "RGBA":
            img2 = cv2.imread(input_image_path, cv2.IMREAD_UNCHANGED)
            img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2BGRA)
            b2, g2, r2, a2 = cv2.split(img2)
            merged = cv2.merge([b2, g2, r2, a])
        else:
            img2 = cv2.cvtColor(np.array(input_image.convert('RGBA')), cv2.COLOR_RGB2BGRA)
            b2, g2, r2, a2 = cv2.split(img2)
            merged = cv2.merge([b2, g2, r2, a])
        try:
            cv2.imwrite(outfile, merged)
        except Exception as e:
            print('##Err处理图片时出现错误： {}'.format(e))
            merged = cv2.cvtColor(merged, cv2.COLOR_RGBA2RGB)
            cv2.imwrite(outfile, merged)


def content_moderations(client, mode_type: int = 1, url: str = "", input_Text: str = ""):
    """ OpenAI GPT 检测模型检测内容是否违法，支持图片与文本
    返回值 return true 检测到内容不合格，
    返回值 return false 没检测到非法内容
    #[client] OpenAI GPT 对象
    #[mode_type] 默认 1
        - 1 图片路径 url(https://xxx)，
        - 2 文本与图片，params(url),input_Text(txt)
        - 3 文本 params(txt)
    #[url] 为图片路径 url
    #[input_Text] 可选，文本内容

    # 1 示例：content_moderations(client,1,'https://xxx')
    # 2 示例: content_moderations(client,2,'https://xxx', string)
    # 3 示例: content_moderations(client,3,string)
    """

    try:
        if mode_type == 1:
            inputs = [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": url,
                    }
                },
            ]
            code = 7106
        elif mode_type == 2:
            inputs = [
                {"type": "text", "text": input_Text},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": url,
                    }
                },
            ]
            code = 7107
        elif mode_type == 3:
            inputs = input_Text
            code = 7105

        response = client.moderations.create(model="omni-moderation-latest", input=inputs)

        is_violation, violations, risk_scores = format_moderation_result(response)
        if is_violation:
            return True, 5, f'{risk_scores}'
        else:
            return False, 1, 'success'
    except Exception as e:
        if mode_type == 1:
            code = 7106
        elif mode_type == 2:
            code = 7107
        elif mode_type == 3:
            code = 7105
        return False, 2, f'{code},GPT检测异常'+str(e)



def format_moderation_result(response):
    """
    处理审核结果，返回是否违规、违规内容和风险分数

    返回值:
        is_violation (bool): 是否存在违规内容
        violations (list): 违规内容详情列表，每项包含违规项索引和违规分类
        risk_scores (dict): 违规分类对应的风险分数，格式 {分类: 分数}
    """
    is_violation = False  # 整体是否违规
    violations = []  # 违规内容详情
    risk_scores = {}  # 风险分数映射

    # 遍历所有输入项的结果（支持多文本/多图片）
    for idx, item_result in enumerate(response.results, 1):
        # 提取当前项的违规分类和分数
        categories = item_result.categories.model_dump()
        category_scores = item_result.category_scores.model_dump()

        # 检查当前项是否违规
        if item_result.flagged:
            is_violation = True  # 只要有一项违规，整体标记为违规

            # 收集当前项的违规分类和分数
            item_violations = []
            for cat, flagged in categories.items():
                if flagged:
                    # 格式化分类名称（如 "sexual_explicit" → "Sexual Explicit"）
                    formatted_cat = cat.replace('_', ' ').title()
                    score = round(category_scores[cat], 4)

                    # 记录当前项的违规详情
                    item_violations.append(formatted_cat)
                    # 记录风险分数（若同一分类在多项中出现，取最高分数）
                    if cat not in risk_scores or score > risk_scores[cat]:
                        risk_scores[cat] = score

            # 将当前项的违规信息添加到总列表
            violations.append({
                "item_index": idx,  # 第n项输入
                "violation_categories": item_violations  # 该项目的违规分类
            })

    return is_violation, violations, risk_scores


def image_examine(client, params):
    response = client.moderations.create(
        model="omni-moderation-latest",
        input=[
            {
                "type": "image_url",
                "image_url": {
                    "url": params,
                }
            },
        ],
    )
    if response.results[0].flagged:
        return False
    else:
        return True


def image_age_examine(client, params):
    try:
        response = client.moderations.create(
            model="omni-moderation-latest",
            input=[
                {
                    "type": "image_url",
                    "image_url": {
                        "url": params,
                    }
                },
            ],
        )
        is_violation, violations, risk_scores = format_moderation_result(response)
        if is_violation:
            return True, 5, f'{risk_scores}'
        else:
            return False, 1, 'success'
    except Exception as e:
        return True, 0, "7106,GTP监测请求错误" + str(e)


def resize_image_style_to_base64(input_path, scale_factor=2, resample=Image.LANCZOS):
    """
    先将图片转换为JPG格式，再调整尺寸并返回Base64编码数据

    参数:
        input_path: 输入图片路径
        scale_factor: 缩放倍数(如2.0表示放大2倍)
        resample: 重采样方法(默认为Image.LANCZOS，高质量)

    返回:
        (成功状态, 图片Base64编码/错误信息)
    """
    try:
        # 打开图片并转换为RGB模式（确保所有图片都能转为JPG）
        with Image.open(input_path) as img:
            # 转换为RGB模式，去除Alpha通道（JPG不支持透明通道）
            if img.mode not in ('RGB', 'L'):  # L是灰度图
                img = img.convert('RGB')

            # 保存为临时JPG文件
            temp_path = os.path.splitext(input_path)[0] + '_temp.jpg'
            img.save(temp_path, 'JPEG', quality=95)

            # 重新打开JPG格式的图片
            with Image.open(temp_path) as jpg_img:
                width, height = jpg_img.size
                img_format = 'JPEG'  # 强制使用JPG格式

                # 计算原始缩放尺寸
                scale = scale_factor / 2
                raw_width = int(width * scale)
                raw_height = int(height * scale)

                # 应用尺寸限制（保持宽高比）
                new_width, new_height = apply_style_size_limits(raw_width, raw_height)

                # 调整尺寸
                resized_img = jpg_img.resize((new_width, new_height), resample=resample)

                # 转换为Base64
                buffer = io.BytesIO()
                resized_img.save(buffer, format=img_format, quality=95)
                img_bytes = buffer.getvalue()
                base64_data = base64.b64encode(img_bytes).decode('utf-8')

                print(f"原始尺寸: {width}×{height}, 调整后尺寸: {new_width}×{new_height}")

                # 删除临时文件
                os.remove(temp_path)

                return True, base64_data

    except Exception as e:
        print(f"图片调整失败: {e}")
        return False, str(e)


def image_to_video_compress_image_size(input_path, max_size_mb=5, width_limit=(50, 2127), height_limit=(50, 4063), jpg_quality=99):
    """宽高超过限制时等比例压缩，不支持的格式统一转成jpg，返回Base64编码"""
    try:
        # 不再限制格式，所有格式均尝试处理（最终转为jpg）
        ext = os.path.splitext(input_path.lower())[1]

        if not os.path.exists(input_path):
            return False, f"文件不存在: {input_path}", False

        file_size_bytes = os.path.getsize(input_path)
        file_size_mb = file_size_bytes / (1024 * 1024)

        # 尝试打开图片（PIL支持多种格式，包括tiff等）
        with Image.open(input_path) as img:
            original_width, original_height = img.size
            print(f"原始尺寸: {original_width}×{original_height}, 大小: {file_size_mb:.2f}MB, 原始格式: {ext}")

            # 处理横屏/竖屏限制反转
            current_width_limit, current_height_limit = width_limit, height_limit
            if original_width > original_height and width_limit != height_limit:
                current_width_limit, current_height_limit = height_limit, width_limit
                print(f"检测到横屏图片，交换限制: 宽{current_width_limit}，高{current_height_limit}")

            # 判断是否需要处理（尺寸超限/大小超限/格式非jpg）
            size_exceeded = (
                    original_width < current_width_limit[0] or
                    original_width > current_width_limit[1] or
                    original_height < current_height_limit[0] or
                    original_height > current_height_limit[1]
            )
            size_limit_exceeded = file_size_mb > max_size_mb
            # 非jpg/jpeg格式强制处理（转为jpg）
            format_need_convert = ext.lower() not in ('.jpg', '.jpeg')
            need_process = size_exceeded or size_limit_exceeded or format_need_convert

            if not need_process:
                # 无需处理时直接读取原文件（确保是jpg格式）
                with open(input_path, 'rb') as f:
                    base64_data = base64.b64encode(f.read()).decode('utf-8')
                return True, base64_data, False

            # 计算调整后尺寸
            def calculate_resized_size(w, h):
                min_w, max_w = current_width_limit
                min_h, max_h = current_height_limit
                if w > max_w or h > max_h:
                    ratio = min(max_w / w, max_h / h)
                    new_w, new_h = int(w * ratio), int(h * ratio)
                else:
                    new_w, new_h = w, h
                new_w = max(new_w, min_w)
                new_h = max(new_h, min_h)
                return new_w, new_h

            new_width, new_height = calculate_resized_size(original_width, original_height)

            # 调整尺寸
            resized_img = img
            if (new_width, new_height) != (original_width, original_height):
                resized_img = img.resize((new_width, new_height), Image.LANCZOS)
                print(f"尺寸调整: {original_width}×{original_height} → {new_width}×{new_height}")

            # 处理文件大小（统一转为jpg）
            def process_file_size(img_obj):
                # 临时文件固定为jpg格式
                with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as temp_file:
                    temp_path = temp_file.name

                try:
                    # 处理透明通道（jpg不支持透明，转为白色背景）
                    if img_obj.mode in ('RGBA', 'LA') or (img_obj.mode == 'P' and 'transparency' in img_obj.info):
                        background = Image.new('RGB', img_obj.size, (255, 255, 255))
                        background.paste(img_obj, mask=img_obj.split()[-1] if img_obj.mode == 'RGBA' else None)
                        img_obj = background
                    elif img_obj.mode != 'RGB':
                        img_obj = img_obj.convert('RGB')  # 非RGB模式转为RGB

                    # 统一保存为jpg格式
                    img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)

                    # 动态调整质量以满足大小限制
                    current_quality = jpg_quality
                    while (os.path.getsize(temp_path) > max_size_mb * 1024 * 1024) and current_quality >= 10:
                        current_quality -= 5
                        img_obj.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
                        print(f"动态压缩质量至{current_quality}，大小: {os.path.getsize(temp_path) / (1024 * 1024):.2f}MB")

                    return temp_path
                except Exception as e:
                    if os.path.exists(temp_path):
                        os.remove(temp_path)
                    raise RuntimeError(f"压缩/转换失败: {str(e)}")

            # 处理并获取jpg格式的临时文件
            temp_path = process_file_size(resized_img)
            final_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
            print(f"处理后大小: {final_size_mb:.2f}MB, 格式: .jpg")

            # 读取临时文件并转为base64
            with open(temp_path, 'rb') as f:
                base64_data = base64.b64encode(f.read()).decode('utf-8')
            os.remove(temp_path)  # 清理临时文件
            return True, base64_data, True

    except UnidentifiedImageError:
        return False, "无法识别的图片格式（不支持的图片类型）", False
    except Exception as e:
        return False, f"处理失败: {str(e)}", False


def compress_image_size(input_path, max_size_mb=5, width_limit=(50, 2127), height_limit=(50, 4063), jpg_quality=99):
    """宽高超过限制时等比例压缩，返回Base64编码"""
    try:
        valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.webp')
        ext = os.path.splitext(input_path.lower())[1]
        if ext not in valid_extensions:
            return False, f"不支持的格式，仅支持{', '.join(valid_extensions)}", False

        if not os.path.exists(input_path):
            return False, f"文件不存在: {input_path}", False

        file_size_bytes = os.path.getsize(input_path)
        file_size_mb = file_size_bytes / (1024 * 1024)

        with Image.open(input_path) as img:
            original_width, original_height = img.size
            print(f"原始尺寸: {original_width}×{original_height}, 大小: {file_size_mb:.2f}MB")

            # 处理横屏/竖屏限制反转
            current_width_limit, current_height_limit = width_limit, height_limit
            if original_width > original_height and width_limit != height_limit:
                current_width_limit, current_height_limit = height_limit, width_limit
                print(f"检测到横屏图片，交换限制: 宽{current_width_limit}，高{current_height_limit}")

            # 判断是否需要处理
            size_exceeded = (
                    original_width < current_width_limit[0] or
                    original_width > current_width_limit[1] or
                    original_height < current_height_limit[0] or
                    original_height > current_height_limit[1]
            )
            size_limit_exceeded = file_size_mb > max_size_mb
            need_process = size_exceeded or size_limit_exceeded

            if not need_process:
                with open(input_path, 'rb') as f:
                    base64_data = base64.b64encode(f.read()).decode('utf-8')
                return True, base64_data, False

            # 计算调整后尺寸
            def calculate_resized_size(w, h):
                min_w, max_w = current_width_limit
                min_h, max_h = current_height_limit

                # 情况1：尺寸超过最大值 → 等比例缩小
                if w > max_w or h > max_h:
                    ratio = min(max_w / w, max_h / h)
                    new_w, new_h = int(w * ratio), int(h * ratio)

                # 情况2：尺寸小于最小值 → 等比例放大（核心优化点）
                elif w < min_w or h < min_h:
                    # 计算宽和高分别需要放大的比例
                    ratio_w = min_w / w if w > 0 else 1.0
                    ratio_h = min_h / h if h > 0 else 1.0
                    # 取较大的比例，确保至少一个维度达到最小值
                    ratio = max(ratio_w, ratio_h)
                    new_w, new_h = int(w * ratio), int(h * ratio)

                    # 放大后可能超过最大值，需要二次限制（极端情况处理）
                    if new_w > max_w or new_h > max_h:
                        ratio = min(max_w / new_w, max_h / new_h)
                        new_w, new_h = int(new_w * ratio), int(new_h * ratio)

                # 情况3：尺寸在正常范围内 → 不调整
                else:
                    new_w, new_h = w, h

                # 确保不小于最小值（避免极端情况计算误差）
                new_w = max(new_w, min_w)
                new_h = max(new_h, min_h)
                return new_w, new_h

            new_width, new_height = calculate_resized_size(original_width, original_height)

            # 调整尺寸
            resized_img = img
            if (new_width, new_height) != (original_width, original_height):
                resized_img = img.resize((new_width, new_height), Image.LANCZOS)
                print(f"尺寸调整: {original_width}×{original_height} → {new_width}×{new_height}")

            # 处理文件大小（核心修复：处理RGBA转JPG）
            def process_file_size(img_obj, ext):

                with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as temp_file:
                    temp_path = temp_file.name

                try:
                    # 关键修复：处理RGBA模式（透明通道）
                    # 如果图片有透明通道，先转换为RGB并填充白色背景
                    if img_obj.mode in ('RGBA', 'LA') or (img_obj.mode == 'P' and 'transparency' in img_obj.info):
                        # 创建白色背景的RGB图片
                        background = Image.new('RGB', img_obj.size, (255, 255, 255))
                        # 将透明图片粘贴到白色背景上
                        background.paste(img_obj, mask=img_obj.split()[-1] if img_obj.mode == 'RGBA' else None)
                        img_obj = background  # 替换为处理后的RGB图片
                    elif img_obj.mode != 'RGB':
                        # 其他模式直接转换为RGB
                        img_obj = img_obj.convert('RGB')

                    # 根据格式保存
                    if ext in ('.jpg', '.jpeg'):
                        img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)
                    elif ext == '.bmp':
                        jpg_path = temp_path.replace('.bmp', '.jpg')
                        img_obj.save(jpg_path, 'JPEG', quality=jpg_quality)
                        temp_path = jpg_path
                    elif ext == '.png':
                        img_obj.save(temp_path, 'PNG', compress_level=9)
                        if os.path.getsize(temp_path) > max_size_mb * 1024 * 1024:
                            jpg_path = temp_path.replace('.png', '.jpg')
                            img_obj.save(jpg_path, 'JPEG', quality=jpg_quality)
                            temp_path = jpg_path

                    # 动态调整JPG质量
                    if temp_path.lower().endswith(('.jpg', '.jpeg')):
                        current_quality = jpg_quality
                        while (os.path.getsize(temp_path) > max_size_mb * 1024 * 1024) and current_quality >= 10:
                            current_quality -= 5
                            img_obj.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
                            print(f"动态压缩质量至{current_quality}，大小: {os.path.getsize(temp_path) / (1024 * 1024):.2f}MB")

                    return temp_path
                except Exception as e:
                    os.remove(temp_path)
                    raise RuntimeError(f"压缩失败: {str(e)}")

            temp_path = process_file_size(resized_img, ext)
            final_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
            print(f"处理后大小: {final_size_mb:.2f}MB")

            with open(temp_path, 'rb') as f:
                base64_data = base64.b64encode(f.read()).decode('utf-8')
            os.remove(temp_path)
            return True, base64_data, True

    except UnidentifiedImageError:
        return False, "无法识别的图片格式"
    except Exception as e:
        return False, f"处理失败: {str(e)}"


def apply_size_limits(raw_width, raw_height):
    """
    应用尺寸限制并保持宽高比
    """
    min_width, max_width = 50, 2128
    min_height, max_height = 50, 4064

    width, height = raw_width, raw_height

    # 处理尺寸过小的情况（放大到最小尺寸）
    if width < min_width or height < min_height:
        scale_width = min_width / width if width != 0 else 1.0
        scale_height = min_height / height if height != 0 else 1.0
        scale = max(scale_width, scale_height)
        width = int(round(width * scale))
        height = int(round(height * scale))

    # 处理尺寸过大的情况（缩小到最大尺寸）
    if width > max_width or height > max_height:
        scale_width = max_width / width
        scale_height = max_height / height
        scale = min(scale_width, scale_height)
        width = int(round(width * scale))
        height = int(round(height * scale))

    # 确保尺寸在安全范围内（避免浮点计算误差）
    width = max(min(width, max_width), min_width)
    height = max(min(height, max_height), min_height)

    return width, height


def apply_style_size_limits(raw_width, raw_height):
    """
    应用尺寸限制并保持宽高比
    """
    min_width, max_width = 50, 1024
    min_height, max_height = 50, 2048

    width, height = raw_width, raw_height

    # 处理尺寸过小的情况（放大到最小尺寸）
    if width < min_width or height < min_height:
        scale_width = min_width / width if width != 0 else 1.0
        scale_height = min_height / height if height != 0 else 1.0
        scale = max(scale_width, scale_height)
        width = int(round(width * scale))
        height = int(round(height * scale))

    # 处理尺寸过大的情况（缩小到最大尺寸）
    if width > max_width or height > max_height:
        scale_width = max_width / width
        scale_height = max_height / height
        scale = min(scale_width, scale_height)
        width = int(round(width * scale))
        height = int(round(height * scale))

    # 确保尺寸在安全范围内（避免浮点计算误差）
    width = max(min(width, max_width), min_width)
    height = max(min(height, max_height), min_height)

    return width, height


def handle_exception(e: Exception) -> dict:
    """统一处理异常，返回结构化错误信息，尝试解析嵌套JSON"""
    error_info = {
        "type": type(e).__name__,
        "message": str(e),
        "timestamp": datetime.now().isoformat()
    }

    # 尝试从异常消息中解析JSON
    try:
        error_msg = str(e)

        # 处理字节字符串的字符串表示形式
        if error_msg.startswith("b'") and error_msg.endswith("'"):
            error_msg = error_msg[2:-1]

        json_data = json.loads(error_msg)

        # 提取JSON中的关键信息
        if isinstance(json_data, dict):
            error_info["json_data"] = json_data
            error_info["code"] = json_data.get("code")
            error_info["message"] = json_data.get("message", error_info["message"])
            error_info["request_id"] = json_data.get("request_id")
    except (json.JSONDecodeError, ValueError):
        # 非JSON格式，保留原始错误信息
        pass

    return error_info


def parse_volcengine_error(error_obj):
    """从火山引擎异常对象中提取错误码"""
    try:
        # 1. 获取原始错误消息字符串
        if hasattr(error_obj, 'message'):
            error_msg = error_obj.message
        else:
            error_msg = str(error_obj)

        # 2. 处理字节字符串格式（b'...'）
        if error_msg.startswith(("b'", 'b"')):
            error_str = error_msg.strip("b'\"")  # 移除 b' 和 '
        else:
            error_str = error_msg

        # 3. 处理双重转义（关键修复）
        error_str = re.sub(r'\\\\u', r'\\u', error_str)

        # 4. 尝试解析JSON并提取code
        error_json = json.loads(error_str)
        return error_json.get('code')  # 返回主错误码（如50215）

    except (json.JSONDecodeError, KeyError):
        # 5. 尝试通过正则表达式提取code（作为备选方案）
        match = re.search(r'"code"\s*:\s*(\d+)', error_str)
        if match:
            return int(match.group(1))
        return None  # 无法提取code


# # 图片压缩替换源路径
def compress_image_size_path(input_path, max_size_mb=5, width_limit=(50, 2127), height_limit=(50, 4063), jpg_quality=99, valid_extensions=('.jpg', '.jpeg', '.png', '.bmp')):
    """宽高超过限制时等比例压缩，直接替换原始文件"""
    try:
        valid_extensions = ('.jpg', '.jpeg', '.png', '.webp')
        # valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp')

        ext = os.path.splitext(input_path.lower())[1]
        if ext not in valid_extensions:
            return False, f"不支持的格式，仅支持{', '.join(valid_extensions)}"

        if not os.path.exists(input_path):
            return False, f"文件不存在: {input_path}"

        file_size_bytes = os.path.getsize(input_path)
        file_size_mb = file_size_bytes / (1024 * 1024)

        with Image.open(input_path) as img:
            original_width, original_height = img.size
            print(f"原始尺寸: {original_width}×{original_height}, 大小: {file_size_mb:.2f}MB")

            # 处理横屏/竖屏限制反转
            current_width_limit, current_height_limit = width_limit, height_limit
            if original_width > original_height and width_limit != height_limit:
                current_width_limit, current_height_limit = height_limit, width_limit
                print(f"检测到横屏图片，交换限制: 宽{current_width_limit}，高{current_height_limit}")

            # 判断是否需要处理
            size_exceeded = (
                    original_width < current_width_limit[0] or
                    original_width > current_width_limit[1] or
                    original_height < current_height_limit[0] or
                    original_height > current_height_limit[1]
            )
            size_limit_exceeded = file_size_mb > max_size_mb
            need_process = size_exceeded or size_limit_exceeded

            if not need_process:
                print(f"图片符合要求，无需处理")
                return True, f"图片符合要求，大小: {file_size_mb:.2f}MB"

            # 计算调整后尺寸
            def calculate_resized_size(w, h):
                min_w, max_w = current_width_limit
                min_h, max_h = current_height_limit
                if w > max_w or h > max_h:
                    ratio = min(max_w / w, max_h / h)
                    new_w, new_h = int(w * ratio), int(h * ratio)
                else:
                    new_w, new_h = w, h
                new_w = max(new_w, min_w)
                new_h = max(new_h, min_h)
                return new_w, new_h

            new_width, new_height = calculate_resized_size(original_width, original_height)

            # 调整尺寸
            resized_img = img
            if (new_width, new_height) != (original_width, original_height):
                resized_img = img.resize((new_width, new_height), Image.LANCZOS)
                print(f"尺寸调整: {original_width}×{original_height} → {new_width}×{new_height}")

            # 处理文件大小
            def process_file_size(img_obj, ext):
                # 创建临时文件（指定与目标路径同磁盘，避免跨盘移动）
                target_dir = os.path.dirname(input_path)
                with tempfile.NamedTemporaryFile(suffix=ext, dir=target_dir, delete=False) as temp_file:
                    temp_path = temp_file.name

                try:
                    # 处理RGBA模式
                    if img_obj.mode in ('RGBA', 'LA') or (img_obj.mode == 'P' and 'transparency' in img_obj.info):
                        background = Image.new('RGB', img_obj.size, (255, 255, 255))
                        background.paste(img_obj, mask=img_obj.split()[-1] if img_obj.mode == 'RGBA' else None)
                        img_obj = background
                    elif img_obj.mode != 'RGB':
                        img_obj = img_obj.convert('RGB')

                    # 根据格式保存
                    if ext in ('.jpg', '.jpeg'):
                        img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)
                    elif ext == '.bmp':
                        jpg_path = temp_path.replace('.bmp', '.jpg')
                        img_obj.save(jpg_path, 'JPEG', quality=jpg_quality)
                        os.remove(temp_path)  # 删除原临时文件
                        temp_path = jpg_path
                    elif ext == '.png':
                        img_obj.save(temp_path, 'PNG', compress_level=9)
                        if os.path.getsize(temp_path) > max_size_mb * 1024 * 1024:
                            jpg_path = temp_path.replace('.png', '.jpg')
                            img_obj.save(jpg_path, 'JPEG', quality=jpg_quality)
                            os.remove(temp_path)  # 删除原临时文件
                            temp_path = jpg_path
                            ext = '.jpg'  # 更新扩展名

                    # 动态调整JPG质量
                    if temp_path.lower().endswith(('.jpg', '.jpeg')):
                        current_quality = jpg_quality
                        while (os.path.getsize(temp_path) > max_size_mb * 1024 * 1024) and current_quality >= 10:
                            current_quality -= 5
                            img_obj.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
                            print(f"动态压缩质量至{current_quality}，大小: {os.path.getsize(temp_path) / (1024 * 1024):.2f}MB")

                    return temp_path, ext
                except Exception as e:
                    if os.path.exists(temp_path):
                        os.remove(temp_path)
                    raise RuntimeError(f"压缩失败: {str(e)}")

            temp_path, new_ext = process_file_size(resized_img, ext)
            final_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
            print(f"处理后大小: {final_size_mb:.2f}MB")

            # 替换原始文件（先复制再删除，支持跨磁盘）
            def replace_file(src, dst):
                """安全替换文件，支持跨磁盘"""
                # 先删除目标文件（如果存在）
                if os.path.exists(dst):
                    os.remove(dst)
                # 复制文件
                with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
                    fdst.write(fsrc.read())
                # 删除源文件
                os.remove(src)

            if new_ext != ext:
                # 格式改变，构建新路径
                base_path = os.path.splitext(input_path)[0]
                new_path = base_path + new_ext
                replace_file(temp_path, new_path)
                print(f"格式已从{ext}转换为{new_ext}，新文件路径: {new_path}")
                return True, f"压缩成功，格式已转换为{new_ext}，大小: {final_size_mb:.2f}MB"
            else:
                # 格式未改变，直接替换
                replace_file(temp_path, input_path)
                return True, f"压缩成功，大小: {final_size_mb:.2f}MB"

    except UnidentifiedImageError:
        return False, "无法识别的图片格式"
    except Exception as e:
        return False, f"处理失败: {str(e)}"


def download_image_upload_s3(img_url: str, storage_service: str, bucket_name: str, stored_prefix: str, resolution_value) -> Tuple[bool, str,str, str]:
    try:
        name = str(uuid.uuid4())
        # 修复：正确处理带查询参数的URL，只提取文件扩展名
        from urllib.parse import urlparse
        parsed_url = urlparse(img_url)
        ext = os.path.splitext(parsed_url.path)[-1].lower() or '.jpg'
        save_path = os.path.join("./upload", f"{name}{ext}")

        from utils import sr_s3
        success, err = sr_s3.download_file(
            img_url, save_path, serviceName=storage_service, bucketName=bucket_name
        )
        if not success:
            return False, "","", f"图片下载失败：{err}"

        success, new_path = video_compress_image_size_path(save_path, 10, resolution_value, resolution_value)
        if not success:
            return False, "","", "图片压缩失败"
        success, s3_key = sr_s3.upload_with_format_handling(
            new_file_path=new_path, original_key=img_url, s3_prefixes_path=stored_prefix, service_name=storage_service, bucket_name=bucket_name
        )
        if not success:
            return False, "","", "图片上传失败"
        return True, s3_key,new_path, ""
    except Exception as e:
        return False, "","", f"图片处理异常：{str(e)}"


def _apply_icc_profile(img, heif_file):
    """将 HEIC/HEIF/AVIF 的 ICC profile 转换为 sRGB"""
    if hasattr(heif_file, "color_profile") and heif_file.color_profile:
        try:
            icc_profile = heif_file.color_profile["data"]
            srgb_profile = ImageCms.createProfile("sRGB")
            input_profile = ImageCms.ImageCmsProfile(io.BytesIO(icc_profile))
            img = ImageCms.profileToProfile(img, input_profile, srgb_profile, outputMode="RGB")
            print("已应用 ICC profile → sRGB")
        except Exception as e:
            print(f"ICC 转换失败，使用 fallback RGB: {e}")
            img = img.convert("RGB")
    else:
        if img.mode != "RGB":
            img = img.convert("RGB")
    return img


def _load_image_with_heif(input_path, ext):
    """统一入口：优先 pillow-heif，fallback pyheif"""
    if HAS_PILLOW_HEIF:
        print(f"检测到 {ext.upper()}，使用 pillow-heif 读取: {input_path}")
        img = Image.open(input_path)
        img = ImageOps.exif_transpose(img)
        if img.mode != "RGB":
            img = img.convert("RGB")
        return img

    if HAS_PYHEIF:
        try:
            print(f"检测到 {ext.upper()}，使用 pyheif 读取: {input_path}")
            heif_file = pyheif.read(input_path)
            img = Image.frombytes(
                mode=heif_file.mode,
                size=heif_file.size,
                data=heif_file.data
            )
            img = ImageOps.exif_transpose(img)
            img = _apply_icc_profile(img, heif_file)
            return img
        except HeifError as e:
            raise RuntimeError(f"{ext.upper()} 读取失败: {str(e)}")
    raise RuntimeError("未安装 pillow-heif 或 pyheif，无法处理 HEIC/AVIF！")

def image_to_base64(img_path: str) -> Tuple[bool, str]:
    try:
        import base64
        with open(img_path, "rb") as f:
            base64_str = base64.b64encode(f.read()).decode("utf-8")
        return True, f"data:image/jpeg;base64,{base64_str}"
    except Exception as e:
        return False, f"图片转base64失败：{str(e)}"

def video_compress_image_size_path(
    input_path,
    max_size_mb=5,
    width_limit=(300, 2127),
    height_limit=(300, 4063),
    jpg_quality=99,
    # ✅ 新增 avif 支持
    valid_extensions=('.jpg', '.jpeg', '.png', '.webp', '.heic', '.heif', '.avif')
):
    """
    压缩图片：宽高超过限制时等比例缩放，大小超过限制时降低质量
    - 新增支持 HEIC/HEIF/AVIF 格式（自动转为 PIL Image）
    - 如果原始宽高为奇数，则-1变为偶数
    - 处理结果替换原始文件（可能转格式）
    - 返回 (True, 新路径) 或 (False, 错误信息)
    """
    try:
        if not os.path.exists(input_path):
            return False, f"文件不存在: {input_path}"

        ext = os.path.splitext(input_path.lower())[1]
        original_ext = ext
        if ext not in valid_extensions:
            print(f"警告: 不支持的格式 {ext}，将转换为JPG")
            ext = '.jpg'

        # 1. 加载图片
        if ext in ('.heic', '.heif', '.avif'):
            img = _load_image_with_heif(input_path, ext)
            force_process = True
        else:
            try:
                img = Image.open(input_path)
                img = ImageOps.exif_transpose(img)
            except UnidentifiedImageError:
                return False, "无法识别的图片格式（HEIC/AVIF 需安装 pillow-heif 或 pyheif）"
            force_process = False

        # 2. 基础信息
        original_width, original_height = img.size
        file_size_mb = os.path.getsize(input_path) / (1024 * 1024)
        print(f"原始尺寸: {original_width}×{original_height}, 大小: {file_size_mb:.2f}MB")

        # 3. 修正奇数尺寸
        even_width = original_width - (original_width % 2)
        even_height = original_height - (original_height % 2)
        if (even_width, even_height) != (original_width, original_height):
            print(f"修正奇数尺寸: {original_width}×{original_height} → {even_width}×{even_height}")
            img = img.resize((even_width, even_height), Image.LANCZOS)
            original_width, original_height = even_width, even_height
            force_process = True

        # 4. 横屏时交换宽高限制
        current_width_limit, current_height_limit = width_limit, height_limit
        if original_width > original_height and width_limit != height_limit:
            current_width_limit, current_height_limit = height_limit, width_limit
            print(f"检测到横屏，交换限制: 宽{current_width_limit}, 高{current_height_limit}")

        # 5. 判断是否需要压缩
        size_exceeded = (
            original_width < current_width_limit[0] or
            original_width > current_width_limit[1] or
            original_height < current_height_limit[0] or
            original_height > current_height_limit[1]
        )
        size_limit_exceeded = file_size_mb > max_size_mb

        if not size_exceeded and not size_limit_exceeded and ext == original_ext and not force_process:
            print("图片符合要求，无需处理")
            return True, input_path

        # 6. 计算调整后的尺寸
        def calculate_resized_size(w, h):
            min_w, max_w = current_width_limit
            min_h, max_h = current_height_limit

            # 情况1：尺寸超过最大值 → 等比例缩小
            if w > max_w or h > max_h:
                ratio = min(max_w / w, max_h / h)
                new_w, new_h = int(w * ratio), int(h * ratio)

            # 情况2：尺寸小于最小值 → 等比例放大（核心优化点）
            elif w < min_w or h < min_h:
                # 计算宽和高分别需要放大的比例
                ratio_w = min_w / w if w > 0 else 1.0
                ratio_h = min_h / h if h > 0 else 1.0
                # 取较大的比例，确保至少一个维度达到最小值
                ratio = max(ratio_w, ratio_h)
                new_w, new_h = int(w * ratio), int(h * ratio)

                # 放大后可能超过最大值，需要二次限制（极端情况处理）
                if new_w > max_w or new_h > max_h:
                    ratio = min(max_w / new_w, max_h / new_h)
                    new_w, new_h = int(new_w * ratio), int(new_h * ratio)

            # 情况3：尺寸在正常范围内 → 不调整
            else:
                new_w, new_h = w, h

            # 确保不小于最小值（避免极端情况计算误差）
            new_w = max(new_w, min_w)
            new_h = max(new_h, min_h)
            return new_w, new_h
        new_width, new_height = calculate_resized_size(original_width, original_height)
        if (new_width, new_height) != (original_width, original_height):
            print(f"尺寸调整: {original_width}×{original_height} → {new_width}×{new_height}")
            img = img.resize((new_width, new_height), Image.LANCZOS)
            force_process = True

        # 7. 保存压缩文件
        def save_with_compression(img_obj, ext):
            target_dir = os.path.dirname(input_path)
            with tempfile.NamedTemporaryFile(suffix=ext, dir=target_dir, delete=False) as temp_file:
                temp_path = temp_file.name

            try:
                # RGBA → RGB（白底）
                if img_obj.mode in ('RGBA', 'LA') or (img_obj.mode == 'P' and 'transparency' in img_obj.info):
                    background = Image.new("RGB", img_obj.size, (255, 255, 255))
                    alpha = img_obj.getchannel("A") if "A" in img_obj.getbands() else None
                    background.paste(img_obj, mask=alpha)
                    img_obj = background
                elif img_obj.mode != "RGB":
                    img_obj = img_obj.convert("RGB")

                # 保存文件
                if ext in ('.jpg', '.jpeg'):
                    img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)
                elif ext == '.png':
                    img_obj.save(temp_path, 'PNG', compress_level=9)
                    if os.path.getsize(temp_path) > max_size_mb * 1024 * 1024:
                        temp_path = temp_path.replace('.png', '.jpg')
                        img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)
                        ext = '.jpg'
                else:
                    img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)
                    ext = '.jpg'

                # 动态压缩（JPG）
                if temp_path.lower().endswith(('.jpg', '.jpeg')):
                    current_quality = jpg_quality
                    while os.path.getsize(temp_path) > max_size_mb * 1024 * 1024 and current_quality >= 10:
                        current_quality -= 5
                        img_obj.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
                        print(f"动态压缩至质量={current_quality}, 大小={os.path.getsize(temp_path) / (1024 * 1024):.2f}MB")

                return temp_path, ext
            except Exception as e:
                if os.path.exists(temp_path):
                    os.remove(temp_path)
                raise RuntimeError(f"压缩失败: {str(e)}")

        temp_path, new_ext = save_with_compression(img, ext)
        final_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
        print(f"处理后大小: {final_size_mb:.2f}MB")

        # 8. 替换原始文件
        def replace_file(src, dst):
            if os.path.exists(dst):
                os.remove(dst)
            with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
                fdst.write(fsrc.read())
            os.remove(src)

        if new_ext != original_ext:
            new_path = os.path.splitext(input_path)[0] + new_ext
            replace_file(temp_path, new_path)
            print(f"格式转换: {original_ext} → {new_ext}, 新文件: {new_path}")
            return True, new_path
        else:
            replace_file(temp_path, input_path)
            return True, input_path

    except Exception as e:
        return False, f"处理失败: {str(e)}"



# def video_compress_image_size_path(
#     input_path,
#     max_size_mb=5,
#     width_limit=(300, 2127),
#     height_limit=(300, 4063),
#     jpg_quality=99,
#     # 新增：将 .heic/.heif 加入支持格式列表
#     valid_extensions=('.jpg', '.jpeg', '.png', '.webp', '.heic', '.heif')
# ):
#     """
#     压缩图片：宽高超过限制时等比例缩放，大小超过限制时降低质量
#     - 新增支持 HEIC/HEIF 格式（自动转为 PIL Image）
#     - 如果原始宽高为奇数，则+1变为偶数
#     - 处理结果替换原始文件（可能转格式）
#     - 返回 (True, 新路径) 或 (False, 错误信息)
#     """
#
#     try:
#         # 1. 检查路径与格式
#         if not os.path.exists(input_path):
#             return False, f"文件不存在: {input_path}"
#
#         ext = os.path.splitext(input_path.lower())[1]
#         original_ext = ext
#         # 格式不支持时转JPG（HEIC已在valid_extensions中，不会触发此分支）
#         if ext not in valid_extensions:
#             print(f"警告: 不支持的格式 {ext}，将转换为JPG")
#             ext = '.jpg'
#
#         # 2. 打开图片（新增HEIC读取分支）
#         img = None
#         # 判断是否为HEIC/HEIF格式，且pyheif已安装
#         if pyheif and ext in ('.heic', '.heif'):
#             try:
#                 print(f"检测到 HEIC 格式，使用 pyheif 读取：{input_path}")
#                 # 读取HEIC文件
#                 heif_file = pyheif.read(input_path)
#                 # 转为PIL Image（处理不同色彩模式）
#                 img = Image.frombytes(
#                     mode=heif_file.mode,
#                     size=heif_file.size,
#                     data=heif_file.data
#                 )
#                 img = ImageOps.exif_transpose(img)  # 根据 EXIF 标签自动旋转图片
#                 print("已根据 EXIF 方向标签修正图片旋转状态")
#                 # 优化色彩空间转换逻辑：仅在必要时转换，避免RGB→sRGB的无效操作
#                 if hasattr(heif_file, 'color_profile') and heif_file.color_profile:
#                     # 只在当前模式不是sRGB且不是RGB时才转换（RGB与sRGB多数场景兼容）
#                     if img.mode not in ("RGB", "sRGB"):
#                         try:
#                             img = img.convert("sRGB")
#                             print("HEIC 色彩空间已转为 sRGB，避免颜色偏差")
#                         except ValueError as e:
#                             print(f"警告：色彩空间转换失败（不影响处理）：{str(e)}")
#                     else:
#                         print("HEIC 图片模式为 RGB/sRGB，无需转换色彩空间")
#             except HeifError as e:
#                 return False, f"HEIC 读取失败：{str(e)}（可能是文件损坏或不支持的HEIC版本）"
#             except Exception as e:
#                 return False, f"HEIC 转 PIL Image 失败：{str(e)}"
#         else:
#             # 非HEIC格式，走原有PIL打开逻辑
#             try:
#                 img = Image.open(input_path)
#             except UnidentifiedImageError:
#                 return False, "无法识别的图片格式（HEIC需安装pyheif）"
#
#         # 3. 基础信息获取
#         original_width, original_height = img.size
#         file_size_mb = os.path.getsize(input_path) / (1024 * 1024)
#         print(f"原始尺寸: {original_width}×{original_height}, 大小: {file_size_mb:.2f}MB")
#
#         # 4. 标记是否必须处理（奇数修正、缩放、压缩等情况）
#         force_process = False
#
#         # 5. 宽高为奇数时 +1（原有逻辑，HEIC转来的图片同样适用）
#         even_width = original_width - (original_width % 2)
#         even_height = original_height - (original_height % 2)
#         if (even_width, even_height) != (original_width, original_height):
#             print(f"修正奇数尺寸: {original_width}×{original_height} → {even_width}×{even_height}")
#             img = img.resize((even_width, even_height), Image.LANCZOS)
#             original_width, original_height = even_width, even_height
#             force_process = True  # ✅ 必须重新保存
#
#         # 6. 横屏时交换宽高限制（原有逻辑不变）
#         current_width_limit, current_height_limit = width_limit, height_limit
#         if original_width > original_height and width_limit != height_limit:
#             current_width_limit, current_height_limit = height_limit, width_limit
#             print(f"检测到横屏，交换限制: 宽{current_width_limit}, 高{current_height_limit}")
#
#         # 7. 判断是否需要压缩（原有逻辑不变）
#         size_exceeded = (
#             original_width < current_width_limit[0] or
#             original_width > current_width_limit[1] or
#             original_height < current_height_limit[0] or
#             original_height > current_height_limit[1]
#         )
#         size_limit_exceeded = file_size_mb > max_size_mb
#
#         if not size_exceeded and not size_limit_exceeded and ext == original_ext and not force_process:
#             print("图片符合要求，无需处理")
#             return True, input_path
#
#         # 8. 计算调整后的尺寸（原有逻辑不变）
#         def calculate_resized_size(w, h):
#             min_w, max_w = current_width_limit
#             min_h, max_h = current_height_limit
#             if w > max_w or h > max_h:
#                 ratio = min(max_w / w, max_h / h)
#                 w, h = int(w * ratio), int(h * ratio)
#             w = max(w, min_w)
#             h = max(h, min_h)
#             # 保证偶数
#             w += w % 2
#             h += h % 2
#             return w, h
#
#         new_width, new_height = calculate_resized_size(original_width, original_height)
#         if (new_width, new_height) != (original_width, original_height):
#             print(f"尺寸调整: {original_width}×{original_height} → {new_width}×{new_height}")
#             img = img.resize((new_width, new_height), Image.LANCZOS)
#             force_process = True
#
#         # 9. 处理文件大小（压缩+转格式，原有逻辑不变，HEIC转来的图片同样适用）
#         def save_with_compression(img_obj, ext):
#             target_dir = os.path.dirname(input_path)
#             with tempfile.NamedTemporaryFile(suffix=ext, dir=target_dir, delete=False) as temp_file:
#                 temp_path = temp_file.name
#
#             try:
#                 # RGBA 转 RGB（HEIC的透明通道也会被处理）
#                 if img_obj.mode in ('RGBA', 'LA') or (img_obj.mode == 'P' and 'transparency' in img_obj.info):
#                     background = Image.new('RGB', img_obj.size, (255, 255, 255))
#                     background.paste(img_obj, mask=img_obj.split()[-1] if img_obj.mode == 'RGBA' else None)
#                     img_obj = background
#                 elif img_obj.mode != 'RGB':
#                     img_obj = img_obj.convert('RGB')
#
#                 # 保存文件
#                 if ext in ('.jpg', '.jpeg'):
#                     img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)
#                 elif ext == '.png':
#                     img_obj.save(temp_path, 'PNG', compress_level=9)
#                     if os.path.getsize(temp_path) > max_size_mb * 1024 * 1024:
#                         # PNG 太大转 JPG
#                         temp_path = temp_path.replace('.png', '.jpg')
#                         img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)
#                         ext = '.jpg'
#                 else:
#                     # 其他格式（含HEIC转来的中间格式）默认存为JPG
#                     img_obj.save(temp_path, 'JPEG', quality=jpg_quality, optimize=True)
#                     ext = '.jpg'
#
#                 # 动态压缩（JPG专属）
#                 if temp_path.lower().endswith(('.jpg', '.jpeg')):
#                     current_quality = jpg_quality
#                     while os.path.getsize(temp_path) > max_size_mb * 1024 * 1024 and current_quality >= 10:
#                         current_quality -= 5
#                         img_obj.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
#                         print(f"动态压缩至质量={current_quality}, 大小={os.path.getsize(temp_path)/(1024*1024):.2f}MB")
#
#                 return temp_path, ext
#             except Exception as e:
#                 if os.path.exists(temp_path):
#                     os.remove(temp_path)
#                 raise RuntimeError(f"压缩失败: {str(e)}")
#
#         temp_path, new_ext = save_with_compression(img, ext)
#         final_size_mb = os.path.getsize(temp_path) / (1024 * 1024)
#         print(f"处理后大小: {final_size_mb:.2f}MB")
#
#         # 10. 替换文件（原有逻辑不变）
#         def replace_file(src, dst):
#             if os.path.exists(dst):
#                 os.remove(dst)
#             with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
#                 fdst.write(fsrc.read())
#             os.remove(src)
#
#         if new_ext != original_ext:
#             new_path = os.path.splitext(input_path)[0] + new_ext
#             replace_file(temp_path, new_path)
#             print(f"格式转换: {original_ext} → {new_ext}, 新文件: {new_path}")
#             return True, new_path
#         else:
#             replace_file(temp_path, input_path)
#             return True, input_path
#
#     # 新增：捕获HEIC专属导入错误
#     except NameError as e:
#         if "pyheif" in str(e):
#             return False, "HEIC 处理依赖缺失！请执行 `pip install pyheif` 并安装系统依赖 libheif（Ubuntu: sudo apt install libheif-dev; Mac: brew install libheif）"
#         return False, f"依赖错误: {str(e)}"
#     except UnidentifiedImageError:
#         return False, "无法识别的图片格式（HEIC需安装pyheif）"
#     except Exception as e:
#         return False, f"处理失败: {str(e)}"

def compress_image_to_5mb(input_path):
    """
    将图片压缩至5MB以内，PNG优先转JPG处理

    参数:
        input_path: 图片文件路径

    返回:
        (成功状态, 处理后Base64编码/错误信息, 处理后大小(MB))
    """
    try:
        # 验证输入
        if not os.path.exists(input_path):
            return False, f"文件不存在: {input_path}", 0

        # 检查文件类型
        valid_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.webp')
        ext = os.path.splitext(input_path.lower())[1]
        if ext not in valid_extensions:
            return False, f"不支持的格式，仅支持{', '.join(valid_extensions)}", 0

        # 获取原始文件大小
        file_size_bytes = os.path.getsize(input_path)
        file_size_mb = file_size_bytes / (1024 * 1024)
        print(f"原始大小: {file_size_mb:.2f}MB")

        # 如果小于5MB，直接返回
        if file_size_mb <= 5:
            with open(input_path, 'rb') as f:
                base64_data = base64.b64encode(f.read()).decode('utf-8')
            return True, base64_data, file_size_mb

        # 打开图片
        with Image.open(input_path) as img:
            original_width, original_height = img.size
            print(f"原始尺寸: {original_width}×{original_height}")

            # 处理PNG格式
            if ext == '.png':
                print("PNG格式，先转为JPG处理")
                # 处理透明背景
                if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
                    background = Image.new('RGB', img.size, (255, 255, 255))
                    background.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
                    img = background
                else:
                    img = img.convert('RGB')
                ext = '.jpg'

            # 创建临时文件
            with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as temp_file:
                temp_path = temp_file.name

            try:
                # 目标大小
                target_size = 5 * 1024 * 1024
                current_quality = 90

                # 第一次尝试压缩
                img.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
                current_size = os.path.getsize(temp_path)
                print(f"初始压缩（质量{current_quality}）: {current_size / (1024 * 1024):.2f}MB")

                # 仅调整质量
                while current_quality > 60 and current_size > target_size:
                    current_quality -= 5
                    img.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
                    current_size = os.path.getsize(temp_path)
                    print(f"降低质量至{current_quality}: {current_size / (1024 * 1024):.2f}MB")

                # 如果仍超限，缩小尺寸
                if current_size > target_size:
                    print(f"质量{current_quality}仍超限，开始缩小尺寸")
                    # 计算需要缩小的比例
                    size_ratio = target_size / current_size
                    scale_ratio = size_ratio ** 0.5
                    new_width = int(original_width * scale_ratio)
                    new_height = int(original_height * scale_ratio)
                    # 确保尺寸不小于原图的50%
                    min_width = max(50, int(original_width * 0.5))
                    min_height = max(50, int(original_height * 0.5))
                    new_width = max(new_width, min_width)
                    new_height = max(new_height, min_height)

                    # 调整尺寸
                    resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
                    resized_img.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
                    current_size = os.path.getsize(temp_path)
                    print(f"缩小尺寸至{new_width}×{new_height}（质量{current_quality}）: {current_size / (1024 * 1024):.2f}MB")

                    # 再次微调质量
                    while current_quality > 60 and current_size > target_size:
                        current_quality -= 5
                        resized_img.save(temp_path, 'JPEG', quality=current_quality, optimize=True)
                        current_size = os.path.getsize(temp_path)
                        print(f"再次降低质量至{current_quality}: {current_size / (1024 * 1024):.2f}MB")

                # 最终检查
                final_size_mb = current_size / (1024 * 1024)
                if final_size_mb > 5:
                    print(f"警告：压缩后仍略超5MB（{final_size_mb:.2f}MB）")

                # 转换为Base64
                with open(temp_path, 'rb') as f:
                    base64_data = base64.b64encode(f.read()).decode('utf-8')

                return True, base64_data, final_size_mb

            finally:
                # 清理临时文件
                if os.path.exists(temp_path):
                    os.remove(temp_path)

    except UnidentifiedImageError:
        return False, "无法识别的图片格式", 0
    except Exception as e:
        return False, f"压缩失败: {str(e)}", 0


def download_image_from_url(url, save_dir='./results', filename=None):
    """
    根据URL下载图片并保存到本地

    参数:
        url (str): 图片的URL链接
        save_dir (str): 保存图片的目录，默认是当前目录下的downloaded_images
        filename (str, optional): 自定义保存的文件名，不包含扩展名。若为None则从URL提取或自动生成

    返回:
        str: 成功时返回图片的本地保存路径，失败时返回None
    """
    # 创建保存目录（如果不存在）
    os.makedirs(save_dir, exist_ok=True)

    # 设置请求头，模拟浏览器行为，避免部分网站拒绝访问
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }

    try:
        # 发送GET请求获取图片
        response = requests.get(url, headers=headers, stream=True, timeout=10)
        response.raise_for_status()  # 检查请求是否成功（状态码200）

        # 检查响应内容是否为图片
        content_type = response.headers.get('Content-Type', '')
        if not content_type.startswith('image/'):
            print(f"错误：URL返回的不是图片，Content-Type: {content_type}")
            return None

        # 提取文件扩展名（从Content-Type或URL中）
        if content_type == 'image/jpeg':
            ext = '.jpg'
        elif content_type == 'image/png':
            ext = '.png'
        elif content_type == 'image/gif':
            ext = '.gif'
        else:
            # 从URL中提取扩展名
            parsed_url = urlparse(url)
            path_ext = os.path.splitext(parsed_url.path)[1].lower()
            ext = path_ext if path_ext else '.jpg'  # 默认用jpg

        # 确定文件名
        if filename:
            # 自定义文件名（添加扩展名）
            save_filename = f"{filename}{ext}"
        else:
            # 从URL提取文件名
            parsed_url = urlparse(url)
            url_basename = os.path.basename(parsed_url.path)
            if url_basename:
                # 移除原有扩展名，使用识别到的扩展名
                name_without_ext = os.path.splitext(url_basename)[0]
                save_filename = f"{name_without_ext}{ext}"
            else:
                # URL没有文件名，生成随机文件名
                random_name = str(uuid.uuid4())[:8]
                save_filename = f"{random_name}{ext}"

        # 拼接保存路径
        save_path = os.path.join(save_dir, save_filename)

        # 分块写入文件（适合大文件）
        with open(save_path, 'wb') as f:
            for chunk in response.iter_content(chunk_size=1024 * 1024):  # 1MB chunks
                if chunk:
                    f.write(chunk)

        print(f"图片下载成功：{save_path}")
        return save_path

    except requests.exceptions.HTTPError as e:
        print(f"HTTP错误：{e}")
    except requests.exceptions.ConnectionError:
        print("连接错误：无法连接到服务器")
    except requests.exceptions.Timeout:
        print("超时错误：请求超时")
    except Exception as e:
        print(f"下载失败：{str(e)}")

    return None


def image_path_to_data_url(image_path):
    """
    将图片路径转换为Data URL，通过文件头识别真实格式，解决扩展名不准确的问题
    """
    # 1. 读取文件头部字节（用于识别真实格式）
    try:
        with open(image_path, 'rb') as file:
            # 读取前16字节（足够识别常见图片格式）
            header = file.read(16)
    except Exception as e:
        raise ValueError(f"无法读取图片文件：{e}")

    # 2. 通过文件头判断真实格式（Magic Number映射）
    # 键：文件头特征（字节组），值：对应的MIME类型
    magic_numbers = {
        (0x89, 0x50, 0x4E, 0x47): 'png',  # PNG
        (0xFF, 0xD8, 0xFF): 'jpeg',  # JPEG (JPG)
        (0x47, 0x49, 0x46): 'gif',  # GIF
        (0x42, 0x4D): 'bmp',  # BMP
        (0x52, 0x49, 0x46, 0x46): 'webp'  # WebP
    }

    # 提取头部前n个字节的十进制值，用于匹配
    header_bytes = tuple(header[:len(k)] for k in magic_numbers.keys())
    image_format = None

    # 遍历匹配文件头
    for magic, fmt in magic_numbers.items():
        # 取与magic长度相同的头部字节进行匹配
        if header[:len(magic)] == bytes(magic):
            image_format = fmt
            break

    # 若未匹配到已知格式，使用扩展名作为 fallback（并警告）
    if not image_format:
        # 从扩展名提取格式（作为最后的备选）
        ext = os.path.splitext(image_path)[-1].lower().lstrip('.')
        image_format = {'jpg': 'jpeg', 'jpeg': 'jpeg', 'png': 'png',
                        'gif': 'gif', 'bmp': 'bmp', 'webp': 'webp'}.get(ext, 'jpeg')
        print(f"警告：无法通过文件头识别格式，将使用扩展名'{ext}'作为默认（可能不准确）")

    # 3. 读取文件内容并编码为Base64
    with open(image_path, 'rb') as file:
        file.seek(0)  # 重置文件指针到开头
        encoded = base64.b64encode(file.read()).decode('utf-8')

    # 4. 返回Data URL
    return f"data:image/{image_format};base64,{encoded}"


def calculate_upscaled_dimensions(image_path):
    """
    计算图片等比例放大后的分辨率（最长边不小于1920px）

    参数:
        image_path: 图片文件路径

    返回:
        tuple: (原始宽度, 原始高度, 放大后宽度, 放大后高度)
        若图片无法打开，返回None
    """
    try:
        # 打开图片获取原始尺寸（高效方式，不加载全部像素）
        with Image.open(image_path) as img:
            original_width, original_height = img.size

        # 确定最长边
        max_side = max(original_width, original_height)

        # 如果最长边小于1920，等比例放大到最长边为1920
        if max_side < 1920:
            # 计算放大比例
            scale = 1920 / max_side
            # 计算放大后的尺寸（四舍五入为整数）
            upscaled_width = int(round(original_width * scale))
            upscaled_height = int(round(original_height * scale))
        else:
            # 最长边已超过或等于1920，保持原始尺寸
            upscaled_width, upscaled_height = original_width, original_height

        return upscaled_width, upscaled_height

    except Exception as e:
        print(f"获取图片尺寸失败: {str(e)}")
        return None


def get_video_info(video_path: str) -> dict:
    """获取视频的宽高、时长和帧率信息"""
    try:
        cap = cv2.VideoCapture(video_path)

        # 获取视频的基本信息
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        fps = cap.get(cv2.CAP_PROP_FPS)
        frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))

        # 计算视频时长（秒）
        duration = frame_count / fps if fps > 0 else 0

        cap.release()

        return {
            'width': width,
            'height': height,
            'duration': duration,
            'fps': fps
        }
    except Exception as e:
        print(f"获取视频信息失败：{str(e)}")
        # 返回默认值或部分信息
        return {
            'width': 0,
            'height': 0,
            'duration': None,
            'fps': None
        }


def delete_temp_files(*file_paths):
    """删除临时文件（忽略不存在的文件和目录）"""
    for path in file_paths:
        if not path:
            continue  # 忽略空路径
        if not isinstance(path, str):
            continue  # 忽略非字符串参数
        if not os.path.exists(path):
            continue  # 忽略不存在的路径
        if os.path.isdir(path):
            print(f"跳过删除目录: {path}")
            continue  # 忽略目录，避免误删
        try:
            os.remove(path)
            print(f"已清理临时文件: {path}")
        except Exception as e:
            print(f"清理临时文件失败 {path}: {str(e)}")

# 图片修正逻辑
def image_correction(donwload_Path):
    # # 2. 修正EXIF方向（关键步骤）
    with Image.open(donwload_Path) as img:
        # 读取原始宽高
        original_width, original_height = img.size
        # 应用EXIF方向修正
        corrected_img = ImageOps.exif_transpose(img)
        # 若修正后尺寸变化（说明有旋转），则覆盖保存
        if corrected_img.size != (original_width, original_height):
            corrected_img.save(donwload_Path)
            print(f"已修正图片方向，新尺寸: {corrected_img.size}")
        else:
            print("图片方向无需修正")
